export * from "./fingerprint";
export * from "./send";
import { encode } from "js-base64";
import {
  TraceDataSeverity,
  TraceDataTypes,
  BreadcrumbTypes,
  BreadcrumbsCategorys,
  TraceTypes,
  TraceLevelType,
  TracePerfRating,
  TraceClientTypes,
  BrowserType,
  OnBeforeOptions,
  OnFetchError,
} from "@/core";
import { IBaseTrace, ITraceBaseData, TraceDataFetch, TracePerf } from "index";

/**
 * 根据HTTP状态码获取跟踪数据的严重级别
 *
 * @param status HTTP状态码
 * @returns 返回跟踪数据的严重级别
 */
export const getFetchStatusLevel = (status: number): TraceDataSeverity => {
  if (status >= 500) {
    return TraceDataSeverity.Critical;
  } else if (status >= 400) {
    return TraceDataSeverity.Error;
  } else if (status >= 300) {
    return TraceDataSeverity.Warning;
  } else if (status >= 200) {
    return TraceDataSeverity.Info;
  } else {
    return TraceDataSeverity.Else;
  }
};

/**
 * 获取时间
 */
export const getTimestamp = (): number => Date.now();

/**
 * 判断给定的元素是否是资源加载目标元素
 *
 * @param target 目标元素
 * @returns 如果目标是 HTMLScriptElement、HTMLLinkElement、HTMLImageElement、HTMLVideoElement 或 HTMLAudioElement 之一，则返回 true；否则返回 false
 */
export const isResourceTarget = (target: HTMLElement) =>
  target instanceof HTMLScriptElement ||
  target instanceof HTMLLinkElement ||
  target instanceof HTMLImageElement ||
  target instanceof HTMLVideoElement ||
  target instanceof HTMLAudioElement;

/**
 * 计算给定字符串的哈希值
 *
 * @param str 要计算哈希值的字符串
 * @returns 返回计算后的哈希值
 */
export function hashCode(str: string): number {
  let hash = 0;
  if (str.length === 0) return hash;
  for (let i = 0; i < str.length; i++) {
    const char = str.charCodeAt(i);
    hash = (hash << 5) - hash + char;
    hash |= 0;
  }
  return hash;
}

/**
 * 将数据类型转换为行为类型
 *
 * @param data 数据类型
 * @returns 返回对应的行为类型
 */
export function dataTypes2BreadcrumbsType(data: TraceDataTypes) {
  switch (data) {
    case TraceDataTypes.JAVASCRIPT:
    case TraceDataTypes.UNKNOWN:
    case TraceDataTypes.UNKNOWN_FUNCTION:
    case TraceDataTypes.REACT:
    case TraceDataTypes.VUE:
      return BreadcrumbTypes.CODE_ERROR;
    case TraceDataTypes.PROMISE:
      return BreadcrumbTypes.UNHANDLEDREJECTION;
    case TraceDataTypes.HTTP:
      return BreadcrumbTypes.FETCH;
    case TraceDataTypes.PERF:
    case TraceDataTypes.LOG:
      return BreadcrumbTypes.CONSOLE;
    case TraceDataTypes.RESOURCE:
      return BreadcrumbTypes.RESOURCE;
    case TraceDataTypes.ROUTE:
      return BreadcrumbTypes.ROUTE;
    default:
      return BreadcrumbTypes.CUSTOMER;
  }
}

/**
 * 将数据类别转换为行为类别
 *
 * @param data 数据类别，类型为 TraceDataTypes 枚举
 * @returns 返回对应的行为类别，类型为 BreadcrumbsCategorys 枚举
 */
export function dataCategory2BreadcrumbsCategory(data: TraceDataTypes) {
  switch (data) {
    case TraceDataTypes.JAVASCRIPT:
    case TraceDataTypes.UNKNOWN:
    case TraceDataTypes.UNKNOWN_FUNCTION:
      return BreadcrumbsCategorys.Exception;
    case TraceDataTypes.REACT:
    case TraceDataTypes.VUE:
      return BreadcrumbsCategorys.Lifecycle;
    case TraceDataTypes.PROMISE:
      return BreadcrumbsCategorys.Exception;
    case TraceDataTypes.HTTP:
      return BreadcrumbsCategorys.Http;
    case TraceDataTypes.PERF:
    case TraceDataTypes.LOG:
      return BreadcrumbsCategorys.Debug;
    case TraceDataTypes.RESOURCE:
      return BreadcrumbsCategorys.Http;
    case TraceDataTypes.ROUTE:
      return BreadcrumbsCategorys.Lifecycle;
  }
}

/**
 * 根据给定的类型获取跟踪数据的类型
 *
 * @param type 跟踪数据类型
 * @returns 返回跟踪数据的类型
 */
export function getTraceDataType(type: TraceDataTypes) {
  switch (type) {
    case TraceDataTypes.PROMISE:
    case TraceDataTypes.UNKNOWN:
    case TraceDataTypes.UNKNOWN_FUNCTION:
    case TraceDataTypes.JAVASCRIPT:
      return TraceTypes.CODE_ERROR;
    case TraceDataTypes.REACT:
    case TraceDataTypes.VUE:
    case TraceDataTypes.ROUTE:
      return TraceTypes.EVENT;
    case TraceDataTypes.PERF:
      return TraceTypes.PERF;
    case TraceDataTypes.HTTP:
      return TraceTypes.FETCH;
    case TraceDataTypes.RESOURCE:
      return TraceTypes.RESOURCE;
    default:
      return TraceTypes.CUSTOMER;
  }
}

/**
 * 根据性能数据获取性能级别
 *
 * @param data 性能数据对象
 * @returns 性能级别枚举值
 */
export function getPerfLevel(data: TracePerf): TraceLevelType {
  let level = TraceLevelType.info;
  if (
    data.LCPRating === TracePerfRating.POOR ||
    data.FIDRating === TracePerfRating.POOR ||
    data.FCPRating === TracePerfRating.POOR ||
    data.TTFBRating === TracePerfRating.POOR ||
    data.CLSRating === TracePerfRating.POOR ||
    data.INPRating === TracePerfRating.POOR
  ) {
    level = TraceLevelType.error;
  }
  if (
    data.LCPRating === TracePerfRating.NEEDS_IMPROVEMENT ||
    data.CLSRating === TracePerfRating.NEEDS_IMPROVEMENT ||
    data.FCPRating === TracePerfRating.NEEDS_IMPROVEMENT ||
    data.FIDRating === TracePerfRating.NEEDS_IMPROVEMENT ||
    data.INPRating === TracePerfRating.NEEDS_IMPROVEMENT ||
    data.TTFBRating === TracePerfRating.NEEDS_IMPROVEMENT
  ) {
    level = TraceLevelType.warn;
  }
  return level;
}

/**
 * 根据日志级别获取对应的 TraceLevelType
 *
 * @param level 日志级别
 * @returns 返回对应的 TraceLevelType 类型
 */
export function getTraceDataLevel(level: TraceDataSeverity) {
  switch (level) {
    case TraceDataSeverity.High:
    case TraceDataSeverity.Critical:
    case TraceDataSeverity.Error:
      return TraceLevelType.error;
    case TraceDataSeverity.Warning:
      return TraceLevelType.warn;
    case TraceDataSeverity.Debug:
    case TraceDataSeverity.Low:
      return TraceLevelType.debug;
    default:
      return TraceLevelType.info;
  }
}

/**
 * 初始化网络拦截器
 *
 * @param instance 追踪实例
 * @param target 网络请求类型，fetch 或 xhr
 * @returns 返回包含拦截逻辑的对象
 */
export function initNetworkInterceptor(instance: IBaseTrace, target: "fetch" | "xhr") {
  return {
    appId: instance.appId,
    pagePath: globalThis.location.pathname,
    onError: (message: OnFetchError) => {
      instance.debug && console.log("[onFetchError] message: ", message);
      const traceBaseData: ITraceBaseData = {
        dataId: hashCode(`${message.url}-${message.method}-${message.status}-${message.statusText}`),
        name: `${target}-error`,
        level: TraceDataSeverity.Critical,
        message: "",
        time: getTimestamp(),
        type: TraceDataTypes.HTTP,
      };
      const errorData: TraceDataFetch = {
        ...traceBaseData,
        url: message.url,
        status: message.status,
        message: message.statusText,
        method: message.method,
        body: message.body,
        elapsedTime: message.elapsedTime,
        httpType: target,
      };
      instance.pushTraceQueue(instance.setTraceData(errorData));
    },
    onBefore: (props: OnBeforeOptions) => {
      instance.saveBreadcrumb({
        name: target,
        level: TraceDataSeverity.Normal,
        type: BreadcrumbTypes.FETCH,
        category: BreadcrumbsCategorys.Http,
        message: props.url,
        time: getTimestamp(),
        request: {
          method: props.method,
          url: props.url,
          options: props.options,
        },
      });
    },
    onAfter: (result: Response) => {
      instance.saveBreadcrumb({
        name: target,
        level: TraceDataSeverity.Normal,
        type: BreadcrumbTypes.FETCH,
        category: BreadcrumbsCategorys.Http,
        message: result.status + " " + result.statusText,
        time: getTimestamp(),
        response: {
          status: result.status,
          statusText: result.statusText,
        },
      });
    },
  };
}

/**
 * 生成一个UUID
 *
 * @returns 返回生成的UUID字符串
 */
export function uuid() {
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
    var r = (Math.random() * 16) | 0,
      v = c == "x" ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}

/**
 * 安全地将对象转换为JSON字符串
 *
 * @param obj 要转换的对象
 * @returns 转换后的JSON字符串，如果对象中存在循环引用，则引用部分将被替换为空字符串
 */
export function safeStringify(obj: object): string {
  const set = new Set();
  const str = JSON.stringify(obj, function (_key, value) {
    if (set.has(value)) {
      return "";
    }
    typeof value === "object" && set.add(value);
    return value;
  });
  set.clear();
  return str;
}

/**
 * 生成sw8追踪信息字符串
 *
 * @param options 参数对象
 * @param options.appId 应用ID
 * @param options.herf 目标地址
 * @param options.pagePath 页面路径
 * @returns sw8追踪信息字符串
 */
export function getSw8({ appId, herf, pagePath }: { appId: string; herf: string; pagePath: string }) {
  const traceId = uuid();
  const traceSegmentId = uuid();
  const appVersion = "v1.0.0";
  // 采样（Sample），0 或 1，0 表示上下文存在, 但是可以（也很可能）被忽略；1 表示这个追踪需要采样并发送到后端。
  const sample = 1;
  // 追踪ID（Trace Id），是 BASE64 编码的字符串，其内容是由 . 分割的三个 long 类型值, 表示此追踪的唯一标识。
  const traceIdStr = String(encode(traceId));
  // 父追踪片段ID（Parent trace segment Id），是 BASE64 编码的字符串，其内容是字符串且全局唯一。
  const segmentId = String(encode(traceSegmentId));
  // 父跨度ID（Parent span Id），是一个从 0 开始的整数，这个跨度ID指向父追踪片段（segment）中的父跨度（span）。
  const spanId = 1;
  // 父服务名称（Parent service），是 BASE64 编码的字符串，其内容是一个长度小于或等于50个UTF-8编码的字符串。
  const service = String(encode(appId));
  // 父服务实例标识（Parent service instance），是 BASE64 编码的字符串，其内容是一个长度小于或等于50个UTF-8编码的字符串。
  const instance = String(encode(appVersion));
  // 父服务的端点（Parent endpoint），是 BASE64 编码的字符串，其内容是父追踪片段（segment）中第一个入口跨度（span）的操作名，由长度小于或等于50个UTF-8编码的字符组成。
  const endpoint = String(encode(pagePath));
  // 本请求的目标地址（Peer），是 BASE64 编码的字符串，其内容是客户端用于访问目标服务的网络地址（不一定是 IP + 端口）。
  const peer = String(encode(herf));
  return `${sample}-${traceIdStr}-${segmentId}-${spanId}-${service}-${instance}-${endpoint}-${peer}`;
}

/**
 * 获取浏览器类型
 *
 * @returns 浏览器类型
 */
export function getBrowserType(): BrowserType {
  const userAgent = navigator.userAgent || navigator.vendor || (globalThis as any).opera;

  // 检查是否为移动端浏览器
  if (/Mobile|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent)) {
    return BrowserType.MOBILE;
  }

  // 检查是否为PC浏览器
  if (/Chrome|Firefox|Safari|Opera|IE|Edge/i.test(userAgent) && !/Mobile/i.test(userAgent)) {
    return BrowserType.PC;
  }

  // 检查是否为WebView（这个方法可能不是百分之百准确，因为WebView的用户代理字符串可能会因设备和平台的不同而有所变化）
  if (/Version\/[\d\.]+.*Chrome/.test(userAgent)) {
    // 针对Android WebView
    return BrowserType.WEBVIEW;
  }

  if (/miniProgram/i.test(userAgent)) {
    return BrowserType.MINI_PROGRAM;
  }

  // 如果无法识别，返回未知类型
  return BrowserType.UNKNOWN;
}

export function getClientType(): TraceClientTypes {
  const userAgent = navigator.userAgent || navigator.vendor || (globalThis as any).opera;
  // 安卓检测
  const androidRegex = /Android/i;
  if (androidRegex.test(userAgent)) {
    return TraceClientTypes.ANDROID_H5;
  }

  // iOS 检测
  const iosRegex = /iPhone|iPad|iPod/i;
  if (iosRegex.test(userAgent)) {
    return TraceClientTypes.IOS_H5;
  }

  if (/Chrome|Firefox|Safari|Opera|IE|Edge/i.test(userAgent)) {
    return TraceClientTypes.BROWSER_H5;
  }

  return TraceClientTypes.PC_H5;
}

// 打开 indexedDB
export function openIDB() {
  return new Promise<IDBDatabase>((resolve, reject) => {
    const request = globalThis.indexedDB?.open("_LOG_SDK_DATABASE_", 1);
    request.onerror = (event) => {
      reject(event);
    };
    request.onupgradeneeded = () => {
      const db = request.result;
      if (!db.objectStoreNames.contains("logs")) {
        const store = db.createObjectStore("logs", { keyPath: "traceId" });
        store.transaction.oncomplete = () => resolve(request.result);
      }
    };
    request.onsuccess = () => {
      const db = request.result;
      if (request.transaction) {
        request.transaction.oncomplete = () => resolve(db);
      } else {
        resolve(db);
      }
    };
  });
}
